package com.course.redis.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.course.common.exception.ParameterException;
import com.course.common.util.AssertUtil;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * @PROJECT_NAME: course
 * @DESCRIPTION: 签到业务逻辑层
 * @Author: 涂玄武
 * @DATE: 2021/7/14 14:44
 */
@Service
public class SignService {

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 获取当月用户情况
     * @param userId
     * @param dateStr
     * @return
     */
    public Map<String,Boolean> getSignInfo(Integer userId,String dateStr){
        //获取日期
        Date date = getDate(dateStr);
        //构建Key
        String signKey = buildSignKey(userId, date);
        //构建一个自动排序的Map
        Map<String,Boolean> signInfo = new TreeMap<>();
        //获取某月的总天数(考虑闰年)
        int dayOfMonth = DateUtil.lengthOfMonth(DateUtil.month(date) + 1,
                DateUtil.isLeapYear(DateUtil.dayOfYear(date)));
        //bitfield user:sign:5:202107 u30 0
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                .valueAt(0);
        List<Long> list = redisTemplate.opsForValue().bitField(signKey, bitFieldSubCommands);
        if (CollectionUtils.isEmpty(list)) {
            return signInfo;
        }
        long v = list.get(0) == null ? 0 : list.get(0);
        //从低位到高位进行遍历，为0表示未签到，为1表示已签到
        for (int i = dayOfMonth; i > 0; i--) {
            /*
                签到：yyyy-MM-01 true
                未签到：yyyy-MM-01 false
             */
            LocalDateTime localDateTime = LocalDateTimeUtil.of(date).withDayOfMonth(i);
            boolean flag = v >> 1 << 1 != v;
            signInfo.put(DateUtil.format(localDateTime,"yyyy-MM-dd"),flag);
            v >>= 1;
        }
        return signInfo;
    }

    /**
     * 获取用户签到次数
     * @param userId
     * @param dateStr
     * @return
     */
    public long getSignCount(Integer userId,String dateStr){
        //获取日期
        Date date = getDate(dateStr);
        //构建Key
        String signKey = buildSignKey(userId, date);
        //BITCOUNT user:sign:5:202107
        return (Long) redisTemplate.execute(
                (RedisCallback<Long>) con -> con.bitCount(signKey.getBytes())
        );
    }

    /**
     * 用户签到
     *
     * @param userId
     * @param dateStr
     * @return
     */
    public int doSign(Integer userId, String dateStr) {
        //获取登录用户信息
        //获取日期
        Date date = getDate(dateStr);
        //获取日期相对应的天数，多少号
        int offset = DateUtil.dayOfMonth(date) - 1; //从0开始
        //构建Key user:sign:5:yyyyMM
        String signKey = buildSignKey(userId, date);
        //查看是否已签到
        Boolean isSigned = redisTemplate.opsForValue().getBit(signKey, offset);
        AssertUtil.isTrue(isSigned, "当前日期已完成签到，无需再签!");
        //签到
        redisTemplate.opsForValue().setBit(signKey, offset, true);
        //统计连续签到的次数
        int count = getContinuousSignCount(userId, date);
        return count;
    }

    /**
     * 统计连续签到的次数
     *
     * @param userId
     * @param date
     * @return
     */
    private int getContinuousSignCount(Integer userId, Date date) {
        //获取日期对应的天数，多少号
        int dayOfMonth = DateUtil.dayOfMonth(date);
        //bitfield user:sign:5:202107 u31 0
        String signKey = buildSignKey(userId, date);
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                .valueAt(0);
        List<Long> list = redisTemplate.opsForValue().bitField(signKey, bitFieldSubCommands);
        if (CollectionUtils.isEmpty(list)) {
            return 0;
        }
        int signCount = 0;
        long v = list.get(0) == null ? 0 : list.get(0);
        for (int i = dayOfMonth; i > 0; i--) {
            //右移再左移，如果等于自己说明最低位是0，标识未签到
            if (v >> 1 << 1 == v) {
                //低位 0 且非当天说明连续签到中断了
                if (i != dayOfMonth) break;
            } else {
                signCount ++;
            }
            //右移一位并重新赋值，相当于把最低位丢弃一位
            v >>= 1;
        }
        return signCount;
    }

    /**
     * 构建Key
     *
     * @param userId
     * @param date
     * @return
     */
    private String buildSignKey(Integer userId, Date date) {
        return String.format("user:sign:%d:%s", userId, DateUtil.format(date, "yyyyMM"));
    }

    /**
     * 获取日期
     *
     * @param dateStr
     * @return
     */
    private Date getDate(String dateStr) {
        if (StrUtil.isBlank(dateStr)) {
            return new Date();
        }
        try {
            return DateUtil.parseDate(dateStr);
        } catch (Exception e) {
            throw new ParameterException("请传入yyyy-MM-dd的日期格式");
        }
    }

}
